#pragma once

#include<string>
#include<istream>
#include<ostream>
#include<map>
#include<unordered_map>
#include<stdexcept>
#include<type_traits>

namespace CppIniLib
{
	class ReadIniFileError : public std::ios::failure
	{
	public:
		ReadIniFileError(const std::string& what) noexcept;
	};

	class SaveIniFileError : public std::ios::failure
	{
	public:
		SaveIniFileError(const std::string& what) noexcept;
	};

	class BadIniFileError : public std::invalid_argument
	{
	public:
		BadIniFileError(const std::string& what) noexcept;
	};

	class BadValueError : public std::invalid_argument
	{
	public:
		BadValueError(const std::string& what) noexcept;
	};

	class Section;
	class IniFile;

	using SectionMapType = std::unordered_map<std::string, std::string>;
	class Section : public SectionMapType
	{
	public:
		Section() = default;

		void SaveSection(std::ostream& ofs) const;

		std::string GetValue(const std::string& Key, const std::string& Default, bool WarnIfValueNotFound = false) const;

		template <typename T> requires std::is_arithmetic_v<T>
		T GetValue(const std::string& Key, T Default, bool WarnIfValueNotFound = false) const;
	};

	extern template bool Section::GetValue(const std::string& Key, bool Default, bool WarnIfValueNotFound) const;
	extern template int8_t Section::GetValue(const std::string& Key, int8_t Default, bool WarnIfValueNotFound) const;
	extern template int16_t Section::GetValue(const std::string& Key, int16_t Default, bool WarnIfValueNotFound) const;
	extern template int32_t Section::GetValue(const std::string& Key, int32_t Default, bool WarnIfValueNotFound) const;
	extern template int64_t Section::GetValue(const std::string& Key, int64_t Default, bool WarnIfValueNotFound) const;
	extern template uint8_t Section::GetValue(const std::string& Key, uint8_t Default, bool WarnIfValueNotFound) const;
	extern template uint16_t Section::GetValue(const std::string& Key, uint16_t Default, bool WarnIfValueNotFound) const;
	extern template uint32_t Section::GetValue(const std::string& Key, uint32_t Default, bool WarnIfValueNotFound) const;
	extern template uint64_t Section::GetValue(const std::string& Key, uint64_t Default, bool WarnIfValueNotFound) const;
	extern template float Section::GetValue(const std::string& Key, float Default, bool WarnIfValueNotFound) const;
	extern template double Section::GetValue(const std::string& Key, double Default, bool WarnIfValueNotFound) const;

	using IniMapType = std::unordered_map<std::string, Section>;
	class IniFile : public IniMapType
	{
	public:
		IniFile(std::istream& ifs);
		IniFile(const std::string& IniFilePath, bool allow_missing_file);

		void SaveIniFile(std::ostream& ofs) const;
		void SaveIniFile(const std::string& SavePath) const;

		std::string GetValue(const std::string& Section, const std::string& Key, const std::string& Default, bool WarnIfValueNotFound) const;

		template <typename T> requires std::is_arithmetic_v<T>
		T GetValue(const std::string& Section, const std::string& Key, T Default, bool WarnIfValueNotFound) const;

		const Section& GetSection(const std::string& SecName, bool WarnIfSectionNotFound = false) const;
	};

	extern template bool IniFile::GetValue(const std::string& Section, const std::string& Key, bool Default, bool WarnIfValueNotFound) const;
	extern template int8_t IniFile::GetValue(const std::string& Section, const std::string& Key, int8_t Default, bool WarnIfValueNotFound) const;
	extern template int16_t IniFile::GetValue(const std::string& Section, const std::string& Key, int16_t Default, bool WarnIfValueNotFound) const;
	extern template int32_t IniFile::GetValue(const std::string& Section, const std::string& Key, int32_t Default, bool WarnIfValueNotFound) const;
	extern template int64_t IniFile::GetValue(const std::string& Section, const std::string& Key, int64_t Default, bool WarnIfValueNotFound) const;
	extern template uint8_t IniFile::GetValue(const std::string& Section, const std::string& Key, uint8_t Default, bool WarnIfValueNotFound) const;
	extern template uint16_t IniFile::GetValue(const std::string& Section, const std::string& Key, uint16_t Default, bool WarnIfValueNotFound) const;
	extern template uint32_t IniFile::GetValue(const std::string& Section, const std::string& Key, uint32_t Default, bool WarnIfValueNotFound) const;
	extern template uint64_t IniFile::GetValue(const std::string& Section, const std::string& Key, uint64_t Default, bool WarnIfValueNotFound) const;
	extern template float IniFile::GetValue(const std::string& Section, const std::string& Key, float Default, bool WarnIfValueNotFound) const;
	extern template double IniFile::GetValue(const std::string& Section, const std::string& Key, double Default, bool WarnIfValueNotFound) const;
}

